想不到啊!Forge2DGame中,render的Canvas使用的是所謂的Canvas座標,而不是World座標。
嚴格說起來,今天真正比較具體的進度就這樣一項,(莫名其妙的突發問題耗費你無數精神,雖然克服了以後是寶貴的經驗,但主管和「鐵人賽的規則」才不屌你這些吧!)
從這個經驗中知道了什麼?
「為何用Canvas繪圖在今日遊戲引擎發達的遊戲製作中是幾乎不存在的選項?」原來如此啊!不過這才有挑戰的價值。
儘量還是不要在Forge2DGame的render中繪製物體,複雜程度有點失控。
還是在擴充了BodyComponent的物件中完成繪製後加入Forge2DGame中比較務實。
(對了,這需要頻繁的判斷BodyComponent是否超出螢幕顯示範圍。)
沒有物理引擎時,移動一個物體就是用執行緒(在Flutter-Flame中就是「update」)來計算應該要進行移動的物體的新座標。
(為什麼不是在render時作?因為這會影響render效率,同時如果畫面真的掉禎卡頓時,遊戲運行的結果依舊會正確,雖然物體會發生程度不一的瞬間移動現象。)
有物理引擎時,移動一個物體看似不難,但其實出現了很多模糊曖昧的地方。
例如:物體是被移動?或是被賦予了「動量」?
transform(Vector2 pos){
position.add(pos);
body.applyLinearImpulse(pos);
body.setTransform(position, body.angle);
}
「applyLibearImpulse」就是賦予「動量」,而setTransform則是移動「座標」。
奇妙的是:定義如此不同,但他們都會和其他可以發生碰撞的物體發生碰撞...可是經常發生碰撞結果不正確!例如還沒真的碰到卻發生了旋轉效果,或是明明碰撞了卻直接穿透。
查詢了一下得到的理論性答案是:
因為 Box2D 使用的是離散的物理模擬,也就是說,它並不是連續地模擬物體的運動,而是在每一個時間步長(time step)後更新物體的位置和速度。如果在兩個時間步長之間,一個物體移動的距離大於另一個物體的大小,那麼這兩個物體可能就會發生穿透。
但真正的問題在於「我不該使用這兩種方式來嘗試穩定的移動一個物體」,我應該使用「body.linearVelocity」。
body.linearVelocity = Vector2(x,y);
如果xy值都設為0,物體就會停止移動。被賦予了linearVelocity值的物體運動中如果碰撞到類似牆的東西,則會修正移動的方向,例如:移動方向為「5,5」,但牆壁的垂直向量為「-3,-3」,則物體在撞牆後會貼著牆壁以「2,2」的方向貼著牆壁繼續移動。
(是「2,2」嗎?這部分的數學計算可能需要修正,重點是「它還保有一個運動量」「它會貼著牆壁移動」。)
為什麼會思考這個問題?
因為通常「物體在接收到指令並朝著方向前進時,碰到牆壁的第一個反應是停止移動,除非玩家接續下達強制移動的命令,否則物體不會沿著牆壁摸索方向前進。」
如何讓物體在碰到牆壁時自動停下來,這倒不負責,在Forge2D中使用的是「ContactCallback」,(牆壁與物體都要擴充實作這個類別,並且在FixtureDef中設定「userData=this」。)
但如何強制移動?
(繼續研究、繼續嘗試。)
同時,我還在思考一個有點骨灰的問題,那就是「點擊/選取」的位置就是「物件要移動的目標與終點」嗎?
最早的遊戲都是採用「棋盤」概念的方式,每個物體一次佔用一格,棋盤是正方形、正六角形、菱形...其實不重要。
一個「人型物件」從A格走到隔壁的B格,看似有移動步伐,但那其實是固定的動畫效果,在系統中,它只是移動的一格而已,這裡頭並不存在著「本來移動一格要走四步,但今天「人形物件」選擇只走兩步。」這樣的可能。
所以,用個很憋扭的方式描述這個過程,「你無法隨意點選一個點後,就讓人形物件精準恰好的落腳在那個點上,必須看你點擊的點屬於哪個格子,然後將人行物件移動到該格的定位點中」。
但今天遊戲還保留棋盤這樣的形式,通常僅僅是讓地圖與關卡設計和呈現能夠快速容易,不過技術上早已經能幾乎做到「點選一個點後,就讓人形物件精準恰好的落腳在那個點上。」
這問題的難點在哪裡?
當棋盤格是方形佈局時,就採用四角地位即可。
但如果是六角形或菱形時呢?
這個數學判斷的過程就很奇妙了!
而且「人形物件」的移動或許好解決,因為已經可以「點選一個點後,就讓人形物件精準恰好的落腳在那個點上」,但如果是「點選一個點後,就讓建築物件精準恰好的落腳在那個點上」呢?
建築物件這樣做...好像不太好吧!很多奇怪的問題會發生!會讓遊戲變得不可玩、不好玩。
(只能持續測試了。有比較明確的結論後再回來修正這篇文。)